//
// JTextPaneWriter.java
// Thud
//
// Copyright (c) 2001-2007 Anthony Parker & the THUD team.
// All rights reserved. See LICENSE.TXT for more information.
//
package net.sourceforge.btthud.util;
import javax.swing.JTextPane;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyleContext;
import javax.swing.text.Style;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import java.io.PrintWriter;
import java.io.CharArrayWriter;
/**
* Implements a PrintWriter-style interface to a JTextPane.
*/
public class JTextPaneWriter extends PrintWriter {
private final CharArrayWriter buffer;
private final JTextPane textPane;
private String currStyle;
private String oldStyle = null;
public JTextPaneWriter (final JTextPane textPane) {
super(new CharArrayWriter (), true);
buffer = (CharArrayWriter)out;
this.textPane = textPane;
this.currStyle = StyleContext.DEFAULT_STYLE;
}
//
// Style state setting.
//
// beginStyle() currently doesn't support recursive locking, so it
// can't be nested; be careful of deadlock.
public void beginStyle (final String newStyle) {
synchronized (lock) {
while (oldStyle != null) {
try {
lock.wait();
} catch (final InterruptedException e) {
// Nothing to do.
}
}
oldStyle = getStyle ();
setStyle(newStyle);
}
}
public void endStyle () {
synchronized (lock) {
// TODO: Assert oldStyle != null?
setStyle(oldStyle);
oldStyle = null;
lock.notify();
}
}
public String getStyle () {
return currStyle;
}
public void setStyle (final String newStyle) {
synchronized (lock) {
if (currStyle == newStyle) {
return;
}
flush();
if (newStyle == null) {
currStyle = StyleContext.DEFAULT_STYLE;
} else {
currStyle = newStyle;
}
}
}
//
// Document management.
//
public StyledDocument getDocument () {
return textPane.getStyledDocument ();
}
public boolean hasStyle (final String name) {
if (name == null) {
// We treat null like the DEFAULT_STYLE.
return true;
} else {
return textPane.getStyle(name) != null;
}
}
public void addStyle (final String name, final AttributeSet attrs) {
final Style styleBase = textPane.getStyle(StyleContext.DEFAULT_STYLE);
final Style style = textPane.addStyle(name, styleBase);
style.addAttributes(attrs);
}
public void reset () {
synchronized (lock) {
final StyledDocument doc = getDocument();
try {
doc.remove(0, doc.getLength());
} catch (final BadLocationException e) {
// TODO: We need to worry about concurrent
// modification of the document. getLength()
// could be out of date by the time we call
// remove(), for example. If we synchronize
// all mutation through this Writer, though,
// then that obviously solves the problem.
System.err.println("Reset failed: " + e);
}
}
}
//
// Convenience methods.
//
public void print (final String str, final String style) {
synchronized (lock) {
final String styleTemp = getStyle();
try {
setStyle(style);
print(str);
} finally {
setStyle(styleTemp);
}
}
}
public void println (final String str, final String style) {
synchronized (lock) {
final String styleTemp = getStyle();
try {
setStyle(style);
println(str);
} finally {
setStyle(styleTemp);
}
}
}
//
// PrintWriter extensions.
//
public void println () {
// PrintWriter outputs line.separator, while JTextPane
// expects the single character \n.
//
// This is the only place we need to change this
// behavior, as far as we know, but it depends on the
// details of the PrintWriter implementation.
//
// A better implementation would be to scan the input
// for line.separator, and convert it to '\n'. This
// would add another level of indirection, though.
synchronized (lock) {
write('\n');
flush();
}
}
public void flush () {
//super.flush()?
synchronized (lock) {
final StyledDocument doc = getDocument();
if (buffer.size() == 0) {
// FIXME: This is a hack to force scroll until
// all writes go through this writer.
// TODO: Add a flag to turn scrolling on/off.
textPane.setCaretPosition(doc.getLength());
return;
}
try {
doc.insertString(doc.getLength(),
buffer.toString(),
textPane.getStyle(currStyle));
// TODO: Add a flag to turn scrolling on/off.
textPane.setCaretPosition(doc.getLength());
} catch (final BadLocationException e) {
// TODO: We need to worry about concurrent
// modification of the document. getLength()
// could be out of date by the time we call
// remove(), for example. If we synchronize
// all mutation through this Writer, though,
// then that obviously solves the problem.
System.err.println("Flush failed: " + e);
}
buffer.reset();
}
}
}